using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;
using Apache.Cassandra;
using Cassandraemon.FullText;


namespace Cassandraemon
{


	public static class ByteArrayExtensions
	{
		public static bool In(this byte[] source, params byte[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params sbyte[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params string[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params DateTime[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params TimeSpan[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params bool[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params char[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params short[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params int[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params long[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params ushort[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params uint[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params ulong[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params float[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params double[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params Guid[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		public static bool In(this byte[] source, params Enum[] items)
		{
			return In(source, items.ToList().ConvertAll<byte[]>(x => x.ToCassandraByte()));
		}
		
		internal static bool In(this byte[] source, IEnumerable<byte[]> byteArray)
		{
			if(!byteArray.Any()) return false;
				
			foreach(byte[] p in byteArray)
			{
				if(source.Length == p.Length)				
				{			
					bool result = true;
					for(int i=0; i<source.Length; i++)
					{
						if(source[i] != p[i])
						{
							result = false;
							break;
						}
					}
					if(result) return true;
				}
			}
			
			return false;
		}
		
		
		
		public static bool GreaterThanOrEqual(this byte[] source, object item)
		{
			byte[] byteArray = item.ToCassandraByte();
			
			var count = Math.Min(source.Length, byteArray.Length);
			
			for(int i=0; i<count; i++)
			{
				if(source[i] > byteArray[i]) return true;
				if(source[i] < byteArray[i]) return false;
			}
			
			return source.Length >= byteArray.Length ? true : false;
		}
		
		public static bool LessThanOrEqual(this byte[] source, object item)
		{
			byte[] byteArray = item.ToCassandraByte();
			
			var count = Math.Min(source.Length, byteArray.Length);
			
			for(int i=0; i<count; i++)
			{
				if(source[i] < byteArray[i]) return true;
				if(source[i] > byteArray[i]) return false;
			}
			
			return source.Length <= byteArray.Length ? true : false;
		}
		
		public static bool Between(this byte[] source, object start, object end)
		{
			byte[] startArray = start.ToCassandraByte();
			byte[] endArray = end.ToCassandraByte();
			
			byte[] min;
			byte[] max;
			
			if(startArray.LessThanOrEqual(endArray))
			{
				min = startArray;
				max = endArray;
			}
			else
			{
				min = endArray;
				max = startArray;
			}
			
			if(source.LessThanOrEqual(min)) return false;
			if(source.GreaterThanOrEqual(max)) return false;
			
			return true;
		}
	
		public static bool Match(this byte[] source, IAnalyzer analyzer, string query)
		{
			var parser = new QueryParser();
			if(!parser.Parse(query)) throw new InvalidOperationException("Invalid query.");
			
			return Match(source, analyzer, parser.Match, parser.UnMatch);
		}
		
		public static bool Match(this byte[] source, IAnalyzer analyzer, string[] matchList, string[] unmatchList)
		{
			foreach(var term in matchList)
			{
				if(source.ToUTF8().IndexOf(term) == -1) return false;
			}
			
			foreach(var term in unmatchList)
			{
				if(source.ToUTF8().IndexOf(term) != -1) return false;
			}
			
			return true;
		}	
		
		
		
		public static byte ToByte(this byte[] source)
		{
			return source[0];
		}
		
		public static sbyte ToSByte(this byte[] source)
		{
			return (sbyte)source[0];
		}
		
		public static string ToUTF8(this byte[] source)
		{
			return Encoding.UTF8.GetString(source);
		}
		
		public static DateTime ToDateTime(this byte[] source)
		{
			return new DateTime(BigEndianBitConverter.ToInt64(source, 0));
		}
		
		public static TimeSpan ToTimeSpan(this byte[] source)
		{
			return new TimeSpan(BigEndianBitConverter.ToInt64(source, 0));
		}
		
		public static bool ToBool(this byte[] source)
		{
			return BigEndianBitConverter.ToBoolean(source, 0);
		}
		
		public static char ToChar(this byte[] source)
		{
			return BigEndianBitConverter.ToChar(source, 0);
		}
		
		public static short ToInt16(this byte[] source)
		{
			return BigEndianBitConverter.ToInt16(source, 0);
		}
		
		public static int ToInt32(this byte[] source)
		{
			return BigEndianBitConverter.ToInt32(source, 0);
		}
		
		public static long ToInt64(this byte[] source)
		{
			return BigEndianBitConverter.ToInt64(source, 0);
		}
		
		public static ushort ToUInt16(this byte[] source)
		{
			return BigEndianBitConverter.ToUInt16(source, 0);
		}
		
		public static uint ToUInt32(this byte[] source)
		{
			return BigEndianBitConverter.ToUInt32(source, 0);
		}
		
		public static ulong ToUInt64(this byte[] source)
		{
			return BigEndianBitConverter.ToUInt64(source, 0);
		}
		
		public static float ToSingle(this byte[] source)
		{
			return BigEndianBitConverter.ToSingle(source, 0);
		}
		
		public static double ToDouble(this byte[] source)
		{
			return BigEndianBitConverter.ToDouble(source, 0);
		}
		
		public static Guid ToGuid(this byte[] source)
		{
			return new Guid(source);
		}
		
		public static TEnum ToEnum<TEnum>(this byte[] source)
		{
			return (TEnum)ToEnum(source, typeof(TEnum));
		}
		
		internal static object ToEnum(this byte[] source, Type type)
		{
			switch (Type.GetTypeCode(type))
        	{
        		case TypeCode.Byte :
					return Enum.ToObject(type, source[0]);
        		case TypeCode.SByte:
					return Enum.ToObject(type, (sbyte)source[0]);
        		case TypeCode.Int16 :
					return Enum.ToObject(type, source.ToInt16());
        		case TypeCode.UInt16 :
					return Enum.ToObject(type, source.ToUInt16());
        		case TypeCode.Int32 :
					return Enum.ToObject(type, source.ToInt32());
        		case TypeCode.UInt32 :
					return Enum.ToObject(type, source.ToUInt32());
        		case TypeCode.Int64 :
					return Enum.ToObject(type, source.ToInt64());
        		case TypeCode.UInt64 :
					return Enum.ToObject(type, source.ToUInt64());
        		default:
	        		throw new ArgumentException("Invalid Enum base type.");
        	}
		}
		
		public static T ToObject<T>(this byte[] source)
		{
			return (T)ToObject(source, typeof(T));
		}
		
		internal static object ToObject(this byte[] source, Type type)
		{
			if(type.IsSerializable)
			{
				MemoryStream stream;
				using(stream = new MemoryStream())
				{
					stream.Write(source, 0, source.Length);
					stream.Seek(0, SeekOrigin.Begin);
					BinaryFormatter bf = new BinaryFormatter();
					return bf.Deserialize(stream);
				}
			}
			else
			{
				throw new ArgumentException("Don't specify unserializable data.");
			}
		}
	}
}
